/**
 * Copyright 2010 Tristan Slominski
 *
 * Licensed under the MIT License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.opensource.org/licenses/mit-license.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mindash.handlebars.client.presenters.impl;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Provider;
import com.mindash.handlebars.client.places.AtPlace;
import com.mindash.handlebars.client.presenters.DtoPresenter;
import com.mindash.handlebars.client.services.EventBus;
import com.mindash.handlebars.client.views.ViewFactory;
import com.mindash.handlebars.shared.actions.Action;
import com.mindash.handlebars.shared.actions.DtoRequest;
import com.mindash.handlebars.shared.actions.DtoResponse;
import com.mindash.handlebars.shared.actions.DtosQuery;
import com.mindash.handlebars.shared.actions.DtosQueryResponse;
import com.mindash.handlebars.shared.actions.SuccessResponse;
import com.mindash.handlebars.shared.model.Dto;
import com.mindash.handlebars.shared.model.DtosQueryResult;

/**
 * @author Tristan Slominski
 * 
 */
public abstract class DefaultDtoPresenterImpl<T extends Dto, V extends ViewFactory>
    extends DefaultPresenterImpl<V> implements DtoPresenter<T> {

  /* variables */
  private T dto;
  private DtoResponse dtoResponse;
  private DtosQueryResponse dtosQueryResponse;
  private boolean requestOngoing;
  private List<AtPlace<?>> queryPlaceQueue = new ArrayList<AtPlace<?>>();

  public DefaultDtoPresenterImpl(EventBus eventBus, Provider<V> viewProvider) {
    super(eventBus, viewProvider);
  }

  /**
   * This method is called by the {@link #fetchDto(DtoRequest)} when a response
   * is sent back but it is not a success (i.e. there are errors). The response
   * object is available via {@link #getDtoResponse()}.
   */
  public abstract void handleErrorsInDtoRequest();

  /**
   * This method is called by the {@link #fetchDtosQuery(DtosQuery)} when a
   * response is sent back but it is not a success (i.e. there are errors). The
   * response is available via {@link #getDtosQueryResponse()}.
   */
  public abstract void handleErrorsInDtosQueryResponse();

  /**
   * This method is called by one of the fetch methods when the presenter for
   * some reason loses track of requests that are sent out and returned.
   */
  public abstract void handleRequestQueuingError();

  /**
   * Executes the request by calling {@link RpcService}.
   * 
   * @param <A>
   * @param <R>
   * @param action the action to call, either {@link DtoRequest} or
   * {@link DtosQuery}
   */
  public <A extends Action<R>, R extends SuccessResponse> void executeRequest(
      final A action) {
    setDtoResponse(null);
    setDtosQueryResponse(null);
    setRequestOngoing(true);
    getQueryPlaceQueue().add(getCurrentPlace());
    rpcService.execute(action, new AsyncCallback<R>() {

      @Override
      public void onFailure(Throwable caught) {
        forceOnAtPlaceExecution();
        setRequestOngoing(false);
        getQueryPlaceQueue().remove(0);
      }

      @Override
      public void onSuccess(R result) {
        if (getQueryPlaceQueue().isEmpty()) {
          handleRequestQueuingError();
        }
        // make sure that if the user goes somewhere else while the request is
        // in progress, we don't interrupt the new location with old data
        else if (getQueryPlaceQueue().get(0).equals(getCurrentPlace())) {
          setResponse(action, result);
          forceOnAtPlaceExecution();
          onAtPlace(getCurrentPlace());
        } else {
          getQueryPlaceQueue().remove(0);
        }
      }

    });
  }

  public <R extends SuccessResponse, A extends Action<R>> Object fetch(
      final A action) {
    if (!isRequestOngoing()) {
      executeRequest(action);
      return null;
    } else {
      if (getQueryPlaceQueue().isEmpty()) {
        handleRequestQueuingError();
        return null;
      } else if (getQueryPlaceQueue().get(0).equals(getCurrentPlace())) {
        getQueryPlaceQueue().remove(0);
        return returnResult(action);
      } else {
        setRequestOngoing(false);
        return fetch(action);
      }
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public T fetchDto(DtoRequest action) {
    return (T) fetch(action);
  }

  @Override
  public DtosQueryResult fetchDtosQuery(DtosQuery action) {
    return (DtosQueryResult) fetch(action);
  }

  public <A extends Action<?>> Object returnResult(A action) {
    if (action instanceof DtoRequest) {
      if (getDtoResponse() == null) {
        return null;
      }
    } else {
      if (getDtosQueryResponse() == null) {
        return null;
      }
    }

    setRequestOngoing(false);
    if (action instanceof DtoRequest) {
      if (getDtoResponse().isSuccess()) {
        return getDtoResponse().getDto();
      }
    } else {
      if (getDtosQueryResponse().isSuccess()) {
        return getDtosQueryResponse().getDtosQueryResult();
      }
    }

    // if we're here then we got a response but it wasn't a success
    if (action instanceof DtoRequest) {
      handleErrorsInDtoRequest();
    } else {
      handleErrorsInDtosQueryResponse();
    }

    return null;
  }

  public <R extends SuccessResponse, A extends Action<R>> void setResponse(
      final A action, R result) {
    if (action instanceof DtoRequest) {
      setDtoResponse((DtoResponse) result);
    } else {
      setDtosQueryResponse((DtosQueryResponse) result);
    }
  }

  /* getters and setters */

  @Override
  public T getDto() {
    return dto;
  }

  @Override
  public DtoResponse getDtoResponse() {
    return dtoResponse;
  }

  public List<AtPlace<?>> getQueryPlaceQueue() {
    return queryPlaceQueue;
  }

  public boolean isRequestOngoing() {
    return requestOngoing;
  }

  @Override
  public void setDto(T dto) {
    this.dto = dto;
  }

  public void setDtoResponse(DtoResponse dtoResponse) {
    this.dtoResponse = dtoResponse;
  }

  public void setRequestOngoing(boolean requestOngoing) {
    this.requestOngoing = requestOngoing;
  }

  public DtosQueryResponse getDtosQueryResponse() {
    return dtosQueryResponse;
  }

  public void setDtosQueryResponse(DtosQueryResponse dtosQueryResponse) {
    this.dtosQueryResponse = dtosQueryResponse;
  }

}
